/*
   Copyright 2020 The Compose Specification Authors.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package loader

import "reflect"
import "fmt"
import "strings"
import "regexp"
import "github.com/pkg/errors"


// comparable to yaml.Unmarshaler, decoder allow a type to define it's own custom logic to convert value
// see https://github.com/mitchellh/mapstructure/pull/294
type decoder interface {
	DecodeMapstructure(interface{}) error
}

// see https://github.com/mitchellh/mapstructure/issues/115#issuecomment-735287466
// adapted to support types derived from built-in types, as DecodeMapstructure would not be able to mutate internal
// value, so need to invoke DecodeMapstructure defined by pointer to type
func decoderHook(from reflect.Value, to reflect.Value) (interface{}, error) {
	// If the destination implements the decoder interface
	u, ok := to.Interface().(decoder)
	if !ok {
		// for non-struct types we need to invoke func (*type) DecodeMapstructure()
		if to.CanAddr() {
			pto := to.Addr()
			u, ok = pto.Interface().(decoder)
		}
		if !ok {
			return from.Interface(), nil
		}
	}
	// If it is nil and a pointer, create and assign the target value first
	if to.Type().Kind() == reflect.Ptr && to.IsNil() {
		to.Set(reflect.New(to.Type().Elem()))
		u = to.Interface().(decoder)
	}
	// Call the custom DecodeMapstructure method

	//by cuijun
	//if _, ok := u.(*types.UnitBytes); ok {
	//	if err:=isValidResourceSizeUnit(from.Interface().(string));err !=nil{
			//return to.Interface(), err
	//	}
	//}

	if err := u.DecodeMapstructure(from.Interface()); err != nil {
		return to.Interface(), err
	}
	return to.Interface(), nil
}

/*
IsValidDiskSizeUnit
检查字符串是否为有效的磁盘大小单位格式，最多支持1位小数，并且支持第二位是0的小数
*/
func isValidResourceSizeUnit(size string) (err error) {
	// 正则表达式匹配最多一位小数的数字后面跟着单位（M, MB, K, KB）
	// 注意：\d 表示数字，[1-9] 表示非零数字
	size = strings.ToUpper(size)
	pattern := `^\d+(\.\d{1,2})?[BKMGTPE][B]?$`
	re := regexp.MustCompile(pattern)

	errUnit := errors.New(fmt.Sprintf("磁盘大小单位格式，最多支持1位小数,如:0.5G,1.5G"))
	// 首先检查是否匹配到任何结果
	if re.MatchString(size) {
		//将字符和单位分开
		sep := strings.LastIndexAny(size, "01234567890.")
		if sep == -1 {
			err = errUnit
			return
		}
		num := size[:sep+1]

		// 如果匹配到了，进一步检查小数点后是否有多于一位数字，并且第二位是否为非0数字
		// 如果不符合这些条件，则不是有效的格式
		if strings.Contains(num, ".") {
			decimalPart := strings.Split(num, ".")
			//用小数点分割为切片
			if len(decimalPart) > 2 {
				err = errUnit
				return
			}

			//小数是一位 正确
			if len(decimalPart[1]) == 1 {
				return
			}

			//小数大于2位 错误
			if len(decimalPart[1]) > 2 {
				err = errUnit
				return
			}

			//小数是2位，并且第二位为非0数字 错误
			slice := strings.Split(decimalPart[1], "")
			if slice[1] != "0" {
				err = errUnit
				return
			}
		}
		return
	}
	err = errors.New(fmt.Sprintf("无效大小单位格式，正确格式为:1M,1G"))
	return
}

